﻿<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional/EN">
<html>
  <head>
    <title>overloading - readme</title>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <link rel="stylesheet" href="main.css">
      <script type="text/javascript" src="highlight.js"></script>
      <script type="text/javascript">
        initHighlightingOnLoad('python', 'cpp');
      </script>
  </head>
  <body>

<h2>Библиотека динамической перегрузки функций и методов основанной на типах аргументов</h2>
<i> Описание составлено на основе версии 0.7.2 </i><br>

<hr>
<ul>
  <a href="#Introduction">Введение</a><br>
  <a href="#SyntaxAndSemantics">Общий синтаксис и семантика</a><br>
  <a href="#PolimorfizmInResolve">Полиморфизм при разрешении перегрузки</a><br>
  <a href="#MethodsOverloading">Перегрузка методов</a><br>
  <a href="#ResolveInInheritance">Особенности разрешения перегрузки методов при наследовании</a><br>
  <a href="#OverrideFunctions">Переопределение функций</a><br>
  <a href="#UsefulFunctions">Полезные функции модуля</a><br>
  <a href="#ResolveStadies">Этапы разрешения перегрузки</a><br>
  <a href="#Optimizations">Производительность и оптимизация</a><br>
  <a href="#Limitations">Ограничения</a><br>
</ul>

<a name="Introduction"><h3>Введение</h3></a>
Библиотека <code>overloading</code> определяет средства позволяющие писать перегруженные функции и методы в вашем коде. Весь функционал реализован в единственном модуле overloading.py. С синтаксисом можно познакомится в главе <a href="#SyntaxAndSemantics">Общий синтаксис и семантика</a>. Здесь я опишу некоторые понятия вносимые библиотекой.<br>
<dl>
  <dt>Перегруженными функциями<dd>являются функции определенные средствами библиотеки(c помощью декораторов <code>overload</code> или <code>when</code>), определенные в одном пространстве имен и имеющие одно имя (исключение - если функция определена с помощью декоратора <code>when</code> функция может иметь любое имя).<br><br>
  <dt>Обобщенной функцией<dd>является функция определенная раньше своих специализированных вариантов (перегруженных функций с тем же именем), но в том же пространстве имен. В пространстве имен может быть определена лишь одна обобщенная функция, остальные будут попросту <a href="#OverrideFunctions">переопределены.</a><br><br>

</dl>
Определения обобщенных и перегруженных методов схожи с определениями функций но есть и <a href="#MethodsOverloading">свои</a> <a href="#ResolveInInheritance">тонкости</a>.<br>

<i>Так-же стоит добавить что библиотека была вдохнавлена <a href="http://python.org/dev/peps/pep-3124/"> PEP 3124</a>, она конечно не такая
навороченная как описанная в PEP'е но необходимый функционал присутствует.</i>
<br>
<!-- У библиотеки есть покрайней мере два применения, это безболезненное расширение существующего кода и написание более расширяемого кода. -->

<a name="SyntaxAndSemantics"><h3>Общий синтаксис и семантика</h3></a>

Для общего случая определения перегруженной функции служит декоратор <code>overload</code> принимающий типы аргументов функции:<br>
<pre><code>
    @overload(int, int)
    def f(x,y):
        pass
</pre></code>
первый параметр декоратора обозначает тип аргумента x, второй соответственно аргумента y. Типы аргументов переданных в <code>overload</code> образуют сигнатуру функции. Следует заметить что количество фактических аргументов функции должно быть равно количеству типов переданных в <code>overload</code>. На C++ определение аналогичной функции выглядит так:
<pre><code>
    void f(int x, int y)
    {}
</pre></code>
<i><b>Замечание:</b> Семантика вообще очень похожа на ее аналог из С++ так что людям знакомым с этим языком будет легко освоится.</i><br><br>
Вызов перегруженной функции ничем не отличается от вызова обычной, если сигнатура функции не совпадает с переданными аргументами и не определенна обобщенная функция то возбуждается исключение <code>CannotResolve</code>, т.е. невозможно разрешить перегрузку:
<pre><code>
    f(0.0, 0.0) # исключение CannotResolve
</pre></code>
Вообще-то определение всего одной перегруженной функции это ведь не то что нам надо, ведь правда? (хотя если ваша цель просто типизировать функцию то это оправданно) Используем библиотеку по прямому назначению и напишем еще парочку функций:
<pre><code>
    @overload(int)
    def f(x):
        ...

    @overload(float)
    def f(x):
        ...

    @overload(complex)
    def f(x):
        ...
</pre></code>
<i><b>Важно:</b> все перегруженные функции должны быть определенны в одном пространстве имен! Определение перегруженной функции вне пространства имен других перегруженных функций или обобщенной функции приведет к <a href="#OverrideFunctions">переопределению перегруженной функции</a> для того пространства имен в котором определена функция если другие перегруженные функции видны в этом пространстве имен. (Это правило не касается <a href="#ResolveInInheritance">перегруженных методов определенных в суперклассе и его подклассах</a>)</i><br><br>

А теперь вызовем <code>f</code> с различными параметрами: 
<pre><code>
    f( 0 )    # вызов специализированной функции с сигнатурой (int)
    f( 0.0 )  # вызов специализированной функции с сигнатурой (float)
    f( 0j )   # вызов специализированной функции с сигнатурой (complex)
    f( "" )   # исключение CannotResolve, невозможно разрешить перегрузку
</pre></code>

Декоратор <code>when</code> позволяет определить перегруженную функцию/метод
имя которой не совпадает с именем других перегруженных методов, пример:
<pre><code>
    @overload(bool)
    def func(x):
        ...
    @overload(int)
    def func(x):
        ...
    @when(func, (list,))
    def func_list(x):
        ...

    func(1)         # вызов функции с сигнатурой func(int)
    func(True)      # вызов функции с сигнатурой func(bool)
    func([1,2,3])   # вызов функции определенной с помощью декоратора when
    func_list([1])  # явный вызов функции
</pre></code>
Первым параметром декоратора должна быть перегруженная функция/метод,
определенная ранее в данном пространстве имен, второй параметр кортеж из
типов аргументов функции.<br><br>

Теперь напишем обобщенную функцию и специализируем ее для различных типов аргументов, например перед нами встала задача написать функцию которая должна инспектировать объект переданный ей в первом аргументе:

<pre><code>
    from overloading import overload
    import types, inspect

    # обобщенная функция
    def inspect_object(obj):
        print "repr:", repr(obj)

    @overload(types.ModuleType)
    def inspect_object(module):
        print "repr:", repr(module)
        print "file:", module.__file__

    @overload(types.FunctionType)
    def inspect_object(function):
        print "repr:", repr(function)
        print "sign:", (function.__name__ + inspect.formatargspec(
            *inspect.getargspec(function)))


    def dummy(x, y=None):
        pass

    inspect_object("test string")
    print
    inspect_object(types)
    print
    inspect_object(dummy)
</pre></code>

<i><b>Важно: </b>Обобщенная функция должна быть определенна раньше специализированных вариантов</i><br><br>

Напечатает примерно следующие:
<pre><code>
repr: 'test string'

repr: &lt;module 'types' from 'D:\bin\Python25\lib\types.pyc'&gt;
file: D:\bin\Python25\lib\types.pyc

repr: &lt;function dummy at 0x00A810B0&gt;
sign: (x, y=None)
</pre></code>

<a name="PolimorfizmInResolve"><h3>Полиморфизм при разрешении перегрузки</h3></a>

При нахождении подходящей функции/метода по переданным параметрам используется полиморфизм, т.е. типы переданных аргументов не должны совпадать с сигнатурой функции/метода, однако аргументы должны быть экземплярами классов созданных путем наследования от типов аргументов описанных в сигнатуре функции, пример:
<pre><code>
    class MyStr(basestring):
        ...

    @overload(basestring)
    def f(x):
        ...

    f("string")
    f(u"unicode string")
    f(MyStr())
</pre></code>
Во всех случаях вызовется перегруженная функция с сигнатурой <code>f(x: basestring)</code>, так как <code>basestring</code> является суперклассом для <code>str</code>, <code>unicode</code> и <code>MyStr</code>.


<a name="MethodsOverloading"><h3>Перегрузка методов</h3></a>

При определении класса с перегруженными методами необходимо наследоваться от класса <code>Overloadable</code> или включить в определение метакласс <code>OverloadableMeta</code>. В остальном определение метода ничем не отличается от определения функций, за исключением того что первый параметр (self) не входит в сигнатуру.<br><br>

Пример определения класса с перегруженными методами:
<pre><code>
    class T(Overloadable):

        @overload(bool)
        def meth(self, x):
            ...
        @overload(int, str)
        def meth(self, x, y):
            ...
    i=T()
    i.meth(1, 'hello') # вызов метода с сигнатурой meth(int, str)
    i.meth(True)       # вызов метода с сигнатурой meth(bool)
    i.meth("Opps!")    # исключение CannotResolve, метод с сигнатурой
                       # meth(x: str) не определен
</pre></code>

<a name="ResolveInInheritance"><h3>Особенности разрешения перегрузки методов при наследовании</h3></a>

Поведение при разрешении перегрузки в классах с перегруженными методами и связанными наследованием отличается от общепринятой тем что в разрешение перегрузки включаются методы из суперклассов например:
<pre><code>
    class A(Overloadable):

        @overload(int)
        def m(self, x):
            ...

    class B(A):

        @overload(object)
        def m(self, x):
            ...

        @overload(list)
        def m(self, x):
            ...

B().m(0) # вызовется метод A.m(int)
</pre></code>
При формировании списка перегруженных методов метакласс <code>OverloadableMeta</code> включает в разрешение перегрузки все перегруженные методы из суперклассов с тем же именем, похожая ситуация происходит и с обобщенными методами.

<a name="OverrideFunctions"><h3>Переопределение функций</h3></a>
Если в одном пространстве имен определенно больше одной перегруженной функции с одинаковыми сигнатурами последняя определенная функция переопределит все предыдущие, при этом будет выдано предупреждение о переопределении, тот же эффект будет если в иерархии наследования будет определенно больше одного метода с одинаковыми сигнатурами. С обобщенными функциями ситуация такая же.<br>
Переопределение иногда бывает полезно при расширении класса с помощью наследования, при этом код в суперклассе останется не тронутым.<br><br>
Пример:
<pre><code>
    class A(Overloadable):

            @overload(float)
            def method(self, x):
                return "float"

            @when(method, (unicode,))
            def method(self, x):
                return "unicode"

            # переопределение метода находящегося в том же пространстве имен
            @overload(float)
            def method(self, x):
                return "overriden float"

    class Base1(object):
        
            def method(self, x):
                return "generic"

    class B(Base1, Overloadable):

            # переопределение обобщенного метода определенного в суперклассе
            def method(self, x):
                return "overriden generic"

            @overload(float)
            def method(self, x):
                return "float"

    class C(A):

            # переопределение метода A.method(unicode) определенного в суперклассе
            @overload(unicode)
            def method(self, x):
                return "overriden unicode"

    test1 = A()
    test2 = B()
    test3 = C()

    test1.method(0.0) # вернет "overriden float"
    test2.method([])  # вернет "overriden generic"
    test3.method(u"") # вернет "overriden unicode"
</pre></code>

<a name="UsefulFunctions"><h3>Полезные функции модуля</h3></a>

<a name="get_function_by_signature"><code>get_function_by_signature(overloaded, signature)</code></a><br>
Функция служит для получения специализированного варианта функции по ее сигнатуре это бывает полезно например для оптимизации многократного вызова функции в цикле. Первым аргументом функции должна быть перегруженная функция, второй аргумент кортеж из типов параметров функции составляющих ее сигнатуру, функция возвращает специализированную функцию или <code>None</code> если подходящей функции не найдено.<br>

Пример использования:
<pre><code>
    @overload(str)
    def f(x):
        print "str"

    @overload(bool)
    def f(x):
        print "bool"

    def work(x, l):
        f_for_bool = get_function_by_signature(f, (x,))
        for i in l:
            f_for_bool(i)

    work(bool, [False, True, False])
</pre></code>
<code>is_overloaded(function)</code><br>
Вспомогательная функция помогающая определить является ли переданная в первом аргументе функция перегруженной

<a name="ResolveStadies"><h3>Этапы разрешения перегрузки</h3></a>

Выбор перегруженной функции происходит в 4 этапа (так как все этапы происходят на этапе исполнения я буду давать сведения об их относительной скорости выполнения):<br>

<ol>
  <li>Просмотр кеша, если перегруженная функция уже вызывалась с идентичными типами аргументов то она уже занесена в кеш, и будет немедленно вызвана, если нет переход к следующему этапу.
  <br><i>Относительно быстрый этап, в котором происходит получение типов всех переданных аргументов и поиск в словаре.</i>

  <li>Подсчитывается количество переданных аргументов, выбираются функции у которых количество принимаемых аргументов равно количеству переданных, в отличии от C++ аргументы по умолчанию не поддерживаются.
  <br><i>Этап относительно быстр, так как всего лишь требуется выполнить <code>len</code> для кортежа переданных аргументов и поиск в словаре.</i>

  <li>Все аргументы сверяются с сигнатурами зарегистрированных перегруженных функций, при проверке
  <a href="#PolimorfizmInResolve">используется полиморфизм</a>. Если после предыдущего этапа среди функций-кандидатов устояла всего одна то вызовется именно она, если же функций-кандидатов больше то алгоритм переходит к следующему этапу.
  <br><i>Медленный этап, так как в цикле вызывается <code>isinstance</code> для каждой пары тип-аргумент, каждой функции-кандидата</i>

  <li>Среди устоявших функций выбор осуществляется с помощью определения самых ближних к типу аргумента, типов в сигнатуре, путем просмотра иерархии наследования, Если выбор среди устоявших функций неоднозначен то возбуждается исключение <code>AmbiguousFunctions</code>.
  <br><i>Очень медленный этап, для каждой пары аргумент-тип определенной в сигнатуре, вызывается функция подсчета количества предков от типа переданного аргумента до типа определенного в сигнатуре, самый тяжелый случай если предки типов образованны путем множественного наследования, тогда функция подсчета предков войдет в рекурсию.</i>
</ol>

<a name="Optimizations"><h3>Производительность и оптимизация</h3></a>
По тестам вызов перегруженной функции в среднем 2-20 раз медленнее вызова обычной функции c ручным разрешением перегрузки с помощью if'ов в теле функции. Откуда такое различие при лучшем и худшем вариантах? Дело в том что при лучшем варианте специализированная функция просто достается из кеша, при худшем отрабатывают <a href="#ResolveStadies">все четыре этапа разрешения перегрузки</a>, четвертый больше всего влияет на производительность, но если предки параметров не образованны путем множественного наследования даже четвертый этап не будет сильно замедлять вызов функции, так же в тяжелых случаях хорошо работает кеш поэтому падение производительности будет только при первом вызове функции, если при последующих вызовах будут использоваться параметры с идентичными типами, то разрешение перегрузки будет раза в 2-3 медленнее ручного варианта. Если вам и такое падение производительности кажется огромным (по сути оно и является огромным но это же динамическая перегрузка, тут другого ждать не приходиться), есть еще вариант оптимизации, который рекомендуется применять в циклах или при частых вызовах, но для этого вы должны знать что во все вызовы используются всего одна-две специализированные функции. Перед циклом нужно вызвать функцию <a href="#get_function_by_signature"><code>get_function_by_signature</code></a> и сохранить результат ее вызова в переменной, после чего использовать сохраненную функцию при последующих вызовах в цикле. При таком подходе происходит прямой вызов специализированной функции, без разрешения перегрузки. Но вы должны быть уверенны что типы аргументов при вызовах не изменятся.


<a name="Limitations"><h3>Ограничения</h3></a>
<ul>
  <li>Нет поддержки аргументов со значениями по умолчанию

  <li>Нет поддержки переменного числа аргументов

  <li>Нет поддержки именованных аргументов

  <li>Нет приведения типов, например <code>int</code> не будет приведен к <code>float</code>

  <li>Декораторы <code>overload</code> и <code>when</code> несовместимы с любыми другими декораторами включая <code>classmethod</code> и <code>staticmethod</code>, по той причине что декораторы определенные перед <code>overload</code> и <code>when</code> портят метаинформацию декорируемых функций. Так же это неверно семантически - так как семантически функция определяется всего одна, остальные перегруженные функции являются специализированными вариантами, если же нужно применить декоратор к функции вместе с ее специализированными вариантами придется забыть о спец. синтаксисе для декораторов и писать примерно так:
  <pre><code>
    @overload(int)
    def f(self, x):
        return "int"

    @overload(str)
    def f(self, x):
        return "str"

    f = some_decorator(f)
  </pre></code>
    к сожалению это не работает с перегруженными методами, по той причине что <code>Overloadable</code> не находит перегруженные методы.
</ul>

  </body>
</html>
